﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Kernel.Impl.Core
{
    using Neo4Net.GraphDb;
    using Neo4Net.GraphDb.Factory;
    using NUnit.Framework;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    [TestFixture]
    public class TestNeo4NetCacheAndPersistence : AbstractNeo4NetTestCase
    {
        private long node1Id = -1;
        private long node2Id = -1;
        private string key1 = "key1";
        private string key2 = "key2";
        private string arrayKey = "arrayKey";
        private int int1 = 1;
        private int int2 = 2;
        private string string1 = "1";
        private string string2 = "2";
        private int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7 };

        [SetUp]
        public void CreateTestingGraph()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            node1Id = node1.Id;
            node2Id = node2.Id;
            node1.SetProperty(key1, int1);
            node1.SetProperty(key2, string1);
            node2.SetProperty(key1, int2);
            node2.SetProperty(key2, string2);
            rel.SetProperty(key1, int1);
            rel.SetProperty(key2, string1);
            node1.SetProperty(arrayKey, array);
            node2.SetProperty(arrayKey, array);
            rel.SetProperty(arrayKey, array);
            //       Assert.That( node1.GetProperty( key1 ).equals( 1 ) , Is.True);
            ITransaction tx = Transaction;
            tx.Success();
            tx.Finish();
            ClearCache();
            tx = GraphDb.BeginTx();
            //        node1.GetPropertyKeys().iterator().next();
            Assert.That(node1.GetProperty(key1), Is.EqualTo(1));
            Transaction = tx;
        }

        [TearDown]
        public void DeleteTestingGraph()
        {
            INode node1 = GraphDb.GetNodeById(node1Id);
            INode node2 = GraphDb.GetNodeById(node2Id);
            node1.GetSingleRelationship(MyRelTypes.Test, Direction.Both).Delete();
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestAddProperty()
        {
            String key3 = "key3";

            INode node1 = GraphDb.GetNodeById(node1Id);
            INode node2 = GraphDb.GetNodeById(node2Id);
            IRelationship rel = node1.GetSingleRelationship(MyRelTypes.Test, Direction.Both);
            // add new property
            node2.SetProperty(key3, int1);
            rel.SetProperty(key3, int2);
            Assert.That(node1.HasProperty(key1), Is.True);
            Assert.That(node2.HasProperty(key1), Is.True);
            Assert.That(node1.HasProperty(key2), Is.True);
            Assert.That(node2.HasProperty(key2), Is.True);
            Assert.That(node1.HasProperty(arrayKey), Is.True);
            Assert.That(node2.HasProperty(arrayKey), Is.True);
            Assert.That(rel.HasProperty(arrayKey), Is.True);
            Assert.That(node1.HasProperty(key3), Is.False);
            Assert.That(node2.HasProperty(key3), Is.True);
            Assert.That(node1.GetProperty(key1), Is.EqualTo(int1));
            Assert.That(node2.GetProperty(key1), Is.EqualTo(int2));
            Assert.That(node1.GetProperty(key2), Is.EqualTo(string1));
            Assert.That(node2.GetProperty(key2), Is.EqualTo(string2));
            Assert.That(rel.GetProperty(key1), Is.EqualTo(int1));
            Assert.That(rel.GetProperty(key2), Is.EqualTo(string1));
            Assert.That(rel.GetProperty(key3), Is.EqualTo(int2));
        }

        [Test]
        public void TestNodeRemoveProperty()
        {
            INode node1 = GraphDb.GetNodeById(node1Id);
            INode node2 = GraphDb.GetNodeById(node2Id);
            IRelationship rel = node1.GetSingleRelationship(MyRelTypes.Test, Direction.Both);

            // test.Remove property
            Assert.That(node1.RemoveProperty(key1), Is.EqualTo(1));
            Assert.That(node2.RemoveProperty(key1), Is.EqualTo(2));
            Assert.That(rel.RemoveProperty(key1), Is.EqualTo(1));
            Assert.That(node1.RemoveProperty(key2), Is.EqualTo(string1));
            Assert.That(node2.RemoveProperty(key2), Is.EqualTo(string2));
            Assert.That(rel.RemoveProperty(key2), Is.EqualTo(string1));
            Assert.That(node1.RemoveProperty(arrayKey), Is.Not.Null);
            Assert.That(node2.RemoveProperty(arrayKey), Is.Not.Null);
            Assert.That(rel.RemoveProperty(arrayKey), Is.Not.Null);
        }

        [Test]
        public void TestNodeChangeProperty()
        {
            INode node1 = GraphDb.GetNodeById(node1Id);
            INode node2 = GraphDb.GetNodeById(node2Id);
            IRelationship rel = node1.GetSingleRelationship(MyRelTypes.Test, Direction.Both);

            // test change property
            node1.SetProperty(key1, int2);
            node2.SetProperty(key1, int1);
            rel.SetProperty(key1, int2);
            int[] newIntArray = new int[] { 3, 2, 1 };
            node1.SetProperty(arrayKey, newIntArray);
            node2.SetProperty(arrayKey, newIntArray);
            rel.SetProperty(arrayKey, newIntArray);
        }

        [Test]
        public void TestNodeGetProperties()
        {
            INode node1 = GraphDb.GetNodeById(node1Id);

            Assert.That(!node1.HasProperty(null), Is.True);
            IEnumerator<object> values = node1.PropertyValues.GetEnumerator();
            values.MoveNext();
            values.MoveNext();
            IEnumerator<string> keys = node1.PropertyKeys.GetEnumerator();
            keys.MoveNext();
            keys.MoveNext();
            Assert.That(node1.HasProperty(key1), Is.True);
            Assert.That(node1.HasProperty(key2), Is.True);
        }

        private IRelationship[] GetRelationshipArray(IEnumerable<IRelationship> relsIterable)
        {
            List<IRelationship> relList = new List<IRelationship>();
            foreach (IRelationship rel in relsIterable)
            {
                relList.Add(rel);
            }
            return relList.ToArray();
        }

        [Test]
        public void TestDirectedRelationship1()
        {
            INode node1 = GraphDb.GetNodeById(node1Id);
            IRelationship rel = node1.GetSingleRelationship(MyRelTypes.Test, Direction.Both);
            INode[] nodes = rel.GetNodes();
            Assert.That(nodes.Length, Is.EqualTo(2));

            INode node2 = GraphDb.GetNodeById(node2Id);
            Assert.That(nodes[0], Is.EqualTo(node1));
            Assert.That(nodes[1], Is.EqualTo(node2));
            Assert.That(rel.GetStartNode(), Is.EqualTo(node1));
            Assert.That(rel.GetEndNode(), Is.EqualTo(node2));

            IRelationship[] relArray = GetRelationshipArray(node1.GetRelationships(MyRelTypes.Test, Direction.Outgoing));
            Assert.That(relArray.Length, Is.EqualTo(1));
            Assert.That(relArray[0], Is.EqualTo(rel));
            relArray = GetRelationshipArray(node2.GetRelationships(MyRelTypes.Test, Direction.Incoming));
            Assert.That(relArray.Length, Is.EqualTo(1));
            Assert.That(relArray[0], Is.EqualTo(rel));
        }

        [Test]
        public void TestRelCountInSameTx()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            Assert.That(GetRelationshipArray(node1.GetRelationships()).Length, Is.EqualTo(1));
            Assert.That(GetRelationshipArray(node2.GetRelationships()).Length, Is.EqualTo(1));
            rel.Delete();
            Assert.That(GetRelationshipArray(node1.GetRelationships()).Length, Is.EqualTo(0));
            Assert.That(GetRelationshipArray(node2.GetRelationships()).Length, Is.EqualTo(0));
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestGetDirectedRelationship()
        {
            INode node1 = GraphDb.GetNodeById(node1Id);
            IRelationship rel = node1.GetSingleRelationship(MyRelTypes.Test, Direction.Outgoing);
            Assert.That(rel.GetProperty(key1), Is.EqualTo(int1));
        }

        [Test]
        public void TestSameTxWithArray()
        {
            Transaction.Success();
            Transaction.Finish();
            NewTransaction();

            INode nodeA = GraphDb.CreateNode();
            INode nodeB = GraphDb.CreateNode();
            IRelationship relA = nodeA.CreateRelationshipTo(nodeB, MyRelTypes.Test);
            nodeA.SetProperty(arrayKey, array);
            relA.SetProperty(arrayKey, array);
            ClearCache();
            Assert.That(nodeA.GetProperty(arrayKey), Is.Not.Null);
            Assert.That(relA.GetProperty(arrayKey), Is.Not.Null);
            relA.Delete();
            nodeA.Delete();
            nodeB.Delete();

        }

        [Test]
        public void TestAddCacheCleared()
        {
            INode nodeA = GraphDb.CreateNode();
            nodeA.SetProperty("1", 1);
            INode nodeB = GraphDb.CreateNode();
            IRelationship rel = nodeA.CreateRelationshipTo(nodeB, MyRelTypes.Test);
            rel.SetProperty("1", 1);
            Transaction.Success();
            Transaction.Finish();
            NewTransaction();
            ClearCache();
            nodeA.CreateRelationshipTo(nodeB, MyRelTypes.Test);
            int count = nodeA.GetRelationships(MyRelTypes.Test).Count();
            Assert.That(count, Is.EqualTo(2));
            nodeA.SetProperty("2", 2);
            Assert.That(nodeA.GetProperty("1"), Is.EqualTo(1));
            rel.SetProperty("2", 2);
            Assert.That(rel.GetProperty("1"), Is.EqualTo(1));
            ClearCache();
            // trigger empty load
            GraphDb.GetNodeById(nodeA.Id);
            GraphDb.GetRelationshipById(rel.Id);
            // apply COW maps
            Transaction.Success();
            Transaction.Finish();
            NewTransaction();
            count = nodeA.GetRelationships(MyRelTypes.Test).Count();
            Assert.That(count, Is.EqualTo(2));
            Assert.That(nodeA.GetProperty("1"), Is.EqualTo(1));
            Assert.That(rel.GetProperty("1"), Is.EqualTo(1));
            Assert.That(nodeA.GetProperty("2"), Is.EqualTo(2));
            Assert.That(rel.GetProperty("2"), Is.EqualTo(2));
        }

        [Test]
        public void TestTxCacheLoadIsolation()
        {
            INode node = GraphDb.CreateNode();
            node.SetProperty("someproptest", "testing");
            INode node1 = GraphDb.CreateNode();
            node1.SetProperty("someotherproptest", 2);
            Commit();
            var txManager = EmbeddedGraphDb.TxManager;

            txManager.Begin();
            node.SetProperty("someotherproptest", "testing2");
            IRelationship rel = node.CreateRelationshipTo(node1,
                MyRelTypes.Test);
            Compat.Transaction.ITransaction txA = txManager.Suspend();
            txManager.Begin();
            Assert.That(node.GetProperty("someproptest"), Is.EqualTo("testing"));
            Assert.That(!node.HasProperty("someotherproptest"), Is.True);
            Assert.That(!node.HasRelationship(), Is.True);
            ClearCache();
            Assert.That(node.GetProperty("someproptest"), Is.EqualTo("testing"));
            Assert.That(!node.HasProperty("someotherproptest"), Is.True);
            Compat.Transaction.ITransaction txB = txManager.Suspend();
            txManager.Resume(txA);
            Assert.That(node.GetProperty("someproptest"), Is.EqualTo("testing"));
            Assert.That(node.HasProperty("someotherproptest"), Is.True);
            Assert.That(node.HasRelationship(), Is.True);
            ClearCache();
            Assert.That(node.GetProperty("someproptest"), Is.EqualTo("testing"));
            Assert.That(node.HasProperty("someotherproptest"), Is.True);
            Assert.That(node.HasRelationship(), Is.True);
            txManager.Suspend();
            txManager.Resume(txB);
            Assert.That(node.GetProperty("someproptest"), Is.EqualTo("testing"));
            Assert.That(!node.HasProperty("someotherproptest"), Is.True);
            Assert.That(!node.HasRelationship(), Is.True);
            txManager.Rollback();
            txManager.Resume(txA);
            node.Delete();
            node1.Delete();
            rel.Delete();
            txManager.Commit();
            NewTransaction();
        }

        [Test]
        public void TestNodeMultiRemoveProperty()
        {
            INode node = GraphDb.CreateNode();
            node.SetProperty("key0", "0");
            node.SetProperty("key1", "1");
            node.SetProperty("key2", "2");
            node.SetProperty("key3", "3");
            node.SetProperty("key4", "4");
            NewTransaction();
            node.RemoveProperty("key3");
            node.RemoveProperty("key2");
            node.RemoveProperty("key3");
            NewTransaction();
            ClearCache();
            Assert.That(node.GetProperty("key0"), Is.EqualTo("0"));
            Assert.That(node.GetProperty("key1"), Is.EqualTo("1"));
            Assert.That(node.GetProperty("key4"), Is.EqualTo("4"));
            Assert.That(node.HasProperty("key2"), Is.False);
            Assert.That(node.HasProperty("key3"), Is.False);
            node.Delete();
        }

        [Test]
        public void TestRelMultiRemoveProperty()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            rel.SetProperty("key0", "0");
            rel.SetProperty("key1", "1");
            rel.SetProperty("key2", "2");
            rel.SetProperty("key3", "3");
            rel.SetProperty("key4", "4");
            NewTransaction();
            rel.RemoveProperty("key3");
            rel.RemoveProperty("key2");
            rel.RemoveProperty("key3");
            NewTransaction();
            ClearCache();
            Assert.That(rel.GetProperty("key0"), Is.EqualTo("0"));
            Assert.That(rel.GetProperty("key1"), Is.EqualTo("1"));
            Assert.That(rel.GetProperty("key4"), Is.EqualTo("4"));
            Assert.That(rel.HasProperty("key2"), Is.False);
            Assert.That(rel.HasProperty("key3"), Is.False);
            rel.Delete();
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestRelationshipCachingIterator()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship[] rels = new IRelationship[100];
            for (int i = 0; i < rels.Length; i++)
            {
                if (i < 50)
                {
                    rels[i] = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
                }
                else
                {
                    rels[i] = node2.CreateRelationshipTo(node1, MyRelTypes.Test);
                }
            }
            NewTransaction();
            ClearCache();
            IEnumerable<IRelationship> relEnumerable = node1.GetRelationships();
            ISet<IRelationship> relSet = new HashSet<IRelationship>();
            foreach (IRelationship rel in rels)
            {
                rel.Delete();
                relSet.Add(rel);
            }
            NewTransaction();
            CollectionAssert.AreEqual(new HashSet<IRelationship>(relEnumerable.ToList()), relSet);
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestLowGrabSize()
        {
            IDictionary<string, string> config = new Dictionary<string, string>();
            config.Add("relationship_grab_size", "1");
            string storePath = GetStorePath("neo2");
            DeleteFileOrDirectory(storePath);
            IGraphDatabaseService graphDb = new GraphDatabaseFactory().NewEmbeddedDatabaseBuilder(storePath).SetConfig(config).NewGraphDatabase();
            GraphDb.ITransaction tx = graphDb.BeginTx();
            INode node1 = graphDb.CreateNode();
            INode node2 = graphDb.CreateNode();
            node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            node2.CreateRelationshipTo(node1, MyRelTypes.Test2);
            node1.CreateRelationshipTo(node2, MyRelTypes.TestTraversal);
            tx.Success();
            tx.Finish();
            tx = graphDb.BeginTx();
            ISet<IRelationship> rels = new HashSet<IRelationship>();
            IRelationshipType[] types = new IRelationshipType[] { MyRelTypes.Test, MyRelTypes.Test2, MyRelTypes.TestTraversal };
            ClearCache();

            foreach (IRelationship rel in node1.GetRelationships(types))
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(3));
            rels.Clear();
            ClearCache();
            foreach (IRelationship rel in node1.GetRelationships())
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(3));

            rels.Clear();
            ClearCache();
            foreach (IRelationship rel in node2.GetRelationships(types))
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(3));
            rels.Clear();
            ClearCache();
            foreach (IRelationship rel in node2.GetRelationships())
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(3));

            rels.Clear();
            ClearCache();
            foreach (IRelationship rel in node1.GetRelationships(Direction.Outgoing))
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(2));
            rels.Clear();
            ClearCache();
            foreach (IRelationship rel in node1.GetRelationships(Direction.Incoming))
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(1));


            rels.Clear();
            ClearCache();
            foreach (IRelationship rel in node2.GetRelationships(Direction.Outgoing))
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(1));
            rels.Clear();
            ClearCache();
            foreach (IRelationship rel in node2.GetRelationships(Direction.Incoming))
            {
                Assert.That(rels.Add(rel), Is.True);
            }
            Assert.That(rels.Count, Is.EqualTo(2));

            tx.Success();
            tx.Finish();
            graphDb.Shutdown();
        }

        [Test]
        public void TestAnotherLowGrabSize()
        {
            TestLowGrabSize(false);
        }

        [Test]
        public void TestAnotherLowGrabSizeWithLoops()
        {
            TestLowGrabSize(true);
        }

        private void TestLowGrabSize(bool includeLoops)
        {
            IDictionary<string, string> config = new Dictionary<string, string>();
            config.Add("relationship_grab_size", "2");
            string storePath = GetStorePath("neo2");
            DeleteFileOrDirectory(storePath);
            IGraphDatabaseService graphDb = new GraphDatabaseFactory().NewEmbeddedDatabaseBuilder(storePath).SetConfig(config).NewGraphDatabase();
            ITransaction tx = graphDb.BeginTx();
            INode node1 = graphDb.CreateNode();
            INode node2 = graphDb.CreateNode();
            INode node3 = graphDb.CreateNode();

            // These are expected relationships for node2
            ICollection<IRelationship> outgoingOriginal = new HashSet<IRelationship>();
            ICollection<IRelationship> incomingOriginal = new HashSet<IRelationship>();
            ICollection<IRelationship> loopsOriginal = new HashSet<IRelationship>();

            int total = 0;
            int totalOneDirection = 0;
            for (int i = 0; i < 33; i++)
            {
                if (includeLoops)
                {
                    loopsOriginal.Add(node2.CreateRelationshipTo(node2, MyRelTypes.Test));
                    total++;
                    totalOneDirection++;
                }

                if (i % 2 == 0)
                {
                    incomingOriginal.Add(node1.CreateRelationshipTo(node2, MyRelTypes.Test));
                    outgoingOriginal.Add(node2.CreateRelationshipTo(node3, MyRelTypes.Test));
                }
                else
                {
                    outgoingOriginal.Add(node2.CreateRelationshipTo(node1, MyRelTypes.Test));
                    incomingOriginal.Add(node3.CreateRelationshipTo(node2, MyRelTypes.Test));
                }
                total += 2;
                totalOneDirection++;
            }
            tx.Success();
            tx.Finish();

            tx = graphDb.BeginTx();
            ISet<IRelationship> rels = new HashSet<IRelationship>();
            ClearCache();

            ICollection<IRelationship> outgoing = new HashSet<IRelationship>(outgoingOriginal);
            ICollection<IRelationship> incoming = new HashSet<IRelationship>(incomingOriginal);
            ICollection<IRelationship> loops = new HashSet<IRelationship>(loopsOriginal);
            foreach (IRelationship rel in node2.GetRelationships(MyRelTypes.Test))
            {
                Assert.That(rels.Add(rel), Is.True);
                if (rel.GetStartNode() == node2 && rel.GetEndNode() == node2)
                {
                    Assert.That(loops.Remove(rel), Is.True);
                }
                else if (rel.GetStartNode() == node2)
                {
                    Assert.That(outgoing.Remove(rel), Is.True);
                }
                else
                {
                    Assert.That(incoming.Remove(rel), Is.True);
                }
            }
            Assert.That(rels.Count, Is.EqualTo(total));
            Assert.That(loops.Count, Is.EqualTo(0));
            Assert.That(incoming.Count, Is.EqualTo(0));
            Assert.That(outgoing.Count, Is.EqualTo(0));
            rels.Clear();

            ClearCache();
            outgoing = new HashSet<IRelationship>(outgoingOriginal);
            incoming = new HashSet<IRelationship>(incomingOriginal);
            loops = new HashSet<IRelationship>(loopsOriginal);
            foreach (IRelationship rel in node2.GetRelationships(Direction.Outgoing))
            {
                Assert.That(rels.Add(rel), Is.True);
                if (rel.GetStartNode() == node2 && rel.GetEndNode() == node2)
                {
                    Assert.That(loops.Remove(rel), Is.True);
                }
                else if (rel.GetStartNode() == node2)
                {
                    Assert.That(outgoing.Remove(rel), Is.True);
                }
                else
                {
                    Assert.Fail("There should be no incomming relationships " + rel);
                }
            }
            Assert.That(rels.Count, Is.EqualTo(totalOneDirection));
            Assert.That(loops.Count, Is.EqualTo(0));
            Assert.That(outgoing.Count, Is.EqualTo(0));
            rels.Clear();

            ClearCache();
            outgoing = new HashSet<IRelationship>(outgoingOriginal);
            incoming = new HashSet<IRelationship>(incomingOriginal);
            loops = new HashSet<IRelationship>(loopsOriginal);
            foreach (IRelationship rel in node2.GetRelationships(Direction.Incoming))
            {
                Assert.That(rels.Add(rel), Is.True);
                if (rel.GetStartNode() == node2 && rel.GetEndNode() == node2)
                {
                    Assert.That(loops.Remove(rel), Is.True);
                }
                else if (rel.GetEndNode() == node2)
                {
                    Assert.That(incoming.Remove(rel), Is.True);
                }
                else
                {
                    Assert.Fail("There should be no outgoing relationships " + rel);
                }
            }
            Assert.That(rels.Count, Is.EqualTo(totalOneDirection));
            Assert.That(loops.Count, Is.EqualTo(0));
            Assert.That(incoming.Count, Is.EqualTo(0));
            rels.Clear();

            tx.Success();
            tx.Finish();
            graphDb.Shutdown();
        }
    }
}
